package com.panopset;

import static com.panopset.Util.ARIAL_PLAIN_10;
import static com.panopset.Util.ARIAL_PLAIN_12;
import static com.panopset.Util.EMPTY_STRING;
import static com.panopset.Util.PERIOD;
import static com.panopset.Util.SINGLE_SPACE;
import static com.panopset.Util.VERSION_SERIAL_NUMBER;
import static com.panopset.Util.dump;
import com.panopset.centralen.resource.Favicon;

import static com.panopset.Util.log;
import static com.panopset.Util.parentFrame;
import static com.panopset.Util.x;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Properties;

import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;

import com.panopset.app.DocumentManager;
import com.panopset.gui.FlexFrame;
import com.panopset.gui.Osgw;
import com.panopset.gui.PopupBox;
import com.panopset.gui.TButton;
import com.panopset.gui.TPanel;
import com.panopset.gui.TypicalDialog;
import com.panopset.gui.container.PanField;

/**
 * Java Swing application foundations.
 *
 * @author Karl Dinwiddie
 */
public abstract class AbstractApplication implements ApplicationInterface {

    private JPanel mainPanel;
    private FlexFrame flexFrame;
    private JMenuBar menuBar;
    private String applicationName;
    private static AbstractApplication app;
    private DocumentManager documentManager;

    public AbstractApplication() {
        setApp(this);
    }

    public static boolean exists() {
        return app != null;
    }

    public static AbstractApplication getApp() {
        return app;
    }

    private static void setApp(AbstractApplication app) {
        AbstractApplication.app = app;
    }

    public static void add(PanField<?> f) {
        if (exists()) {
            getApp().getDocumentManager().getContainers().add(f);
        }
    }

    protected abstract JPanel createMainPanel();

    protected abstract PopupBox getAboutBox();

    /**
     * Override this method if you want a name other than the top level class
     * name that extends AbstractApplication.
     *
     * @return String application name.
     */
    protected String createApplicationName() {
        return this.getClass().getSimpleName();
    }

    public final String getApplicationName() {
        if (applicationName == null) {
            applicationName = createApplicationName();
        }
        return applicationName;
    }

    public DocumentManager getDocumentManager() {
        if (documentManager == null) {
            documentManager = new DocumentManager(PERIOD + getClass().getCanonicalName());
        }
        return documentManager;
    }

    protected JMenuBar getMenuBar() {
        if (menuBar == null) {
            menuBar = new MessageMenuBar();
            menuBar.add(createFileMenu());
            menuBar.add(createHelpMenu());
        }
        return menuBar;
    }

    private JMenu createHelpMenu() {
        JMenu menu = new JMenu("Help");
        menu.setMnemonic(KeyEvent.VK_H);
        menu.setFont(ARIAL_PLAIN_10);

        JMenuItem mi = new JMenuItem(x("About") + SINGLE_SPACE + getApplicationName());
        mi.setMnemonic(KeyEvent.VK_A);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                doAbout();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem(x("Java Properties"));
        mi.setMnemonic(KeyEvent.VK_J);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                doJavaProperties();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem(x("System Information"));
        mi.setMnemonic(KeyEvent.VK_J);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                doSystemInfo();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem(x("Log"));
        mi.setMnemonic(KeyEvent.VK_J);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                doShowLog();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        return menu;
    }

    private JMenu createFileMenu() {
        JMenu menu = new JMenu("File");
        menu.setMnemonic(KeyEvent.VK_F);
        menu.setFont(ARIAL_PLAIN_10);

        JMenuItem mi = new JMenuItem("New");
        mi.setMnemonic(KeyEvent.VK_N);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                getDocumentManager().doNew(
                        AbstractApplication.this);
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem("Open");
        mi.setMnemonic(KeyEvent.VK_O);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                getDocumentManager().doOpen(
                        AbstractApplication.this);
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem("Save");
        mi.setMnemonic(KeyEvent.VK_S);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                getDocumentManager().doSave(
                        AbstractApplication.this);
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem("Save as");
        mi.setMnemonic(KeyEvent.VK_A);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                getDocumentManager().doSaveAs(
                        AbstractApplication.this);
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        mi = new JMenuItem("Quit");
        mi.setMnemonic(KeyEvent.VK_Q);
        mi.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                getFlexFrame().quit();
            }
        });
        mi.setFont(ARIAL_PLAIN_10);
        menu.add(mi);

        return menu;
    }

    /**
     * Override to add menus in between the File and Help menus.
     *
     * @param menuBar
     */
    protected void addMiddleMenus(JMenuBar menuBar) {
        // Nothing to do for default behavior.
    }

    protected JPanel getMainPanel() {
        if (mainPanel == null) {
            mainPanel = createMainPanel();
        }
        return mainPanel;
    }

    class MessageMenuBar extends JMenuBar {

        boolean firstTime = true;

        public MessageMenuBar() {
            super();
            new Thread("painter") {

                @Override
                public void run() {
                    String lastMessage = EMPTY_STRING;
                    while (Osgw.isActive()) {
                        if (firstTime || !Alert.getMessage().equals(lastMessage)) {
                            repaint();
                            lastMessage = Alert.getMessage();
                            firstTime = false;
                        }
                        try {
                            Thread.sleep(200);
                        } catch (InterruptedException e) {
                            log(e);
                        }
                    }
                }
            }.start();
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            g.setColor(Color.GRAY);
            int w = (getSize().width - getLocation().x) / 2;
            int x = getLocation().x + w;
            int y = getLocation().y + 1;
            int h = getSize().height - 2;
            g.fillRect(x, y, w, h);
            g.setColor(Color.BLACK);
            g.fillRect(x + 1, y + 1, w - 2, h - 2);
            g.setFont(ARIAL_PLAIN_12);
            g.setColor(Alert.getColor());
            g.drawString(Alert.getMessage(), w + 3, h - 4);
        }
        private static final long serialVersionUID = VERSION_SERIAL_NUMBER;
    }

    public FlexFrame getFlexFrame() {
        if (flexFrame == null) {
            flexFrame = new FlexFrame.Builder().packageIcon(
                    Favicon.getLatest().getPath())
                    .menuBar(getMenuBar()).title(getApplicationName()).defaultSize(getDefaultSize()).applicationInterface(this).construct();
            flexFrame.getFrame().setLayout(new BorderLayout());
            flexFrame.getFrame().add(BorderLayout.CENTER, getMainPanel());
            TPanel sp = new TPanel(new BorderLayout());
            TButton btn = new TButton(x("Clear"));
            btn.setToolTipText(x("Clear message"));
            btn.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    Alert.clearTopMessage();
                }
            });
            sp.add(BorderLayout.EAST, btn);
            sp.add(BorderLayout.CENTER, Alert.singleton.getMsgLabel());
            flexFrame.getFrame().add(BorderLayout.SOUTH, sp);
            Util.setParentFrame(parentFrame);
        }
        return flexFrame;
    }

    public void quit() {
        save();
        getAboutBox().dispose();
        setApp(null);
        Osgw.close();
    }

    boolean firstTime = true;

    /**
     * Prior to calling this method, make sure that all PanField components are
     * created. If you get a java.util.ConcurrentModificationException when
     * calling this method, it almost certainly means that a PanField subclass
     * was loaded without having first been created.
     *
     * @param value
     */
    public void setVisible(boolean value) {
        if (value == true && firstTime) {
            getMainPanel();
            load();
            firstTime = false;
        }
        getFlexFrame().setVisible(value);
    }

    public synchronized void doSystemInfo() {
        new TypicalDialog.Builder(x("System Information")).centerPanel(
                new SysInfoPanel().getGui()).construct().display();
    }

    public synchronized void doJavaProperties() {
        TypicalDialog.dspdlg("Java System Properties", dump(System.getProperties()));
    }

    public synchronized void doShowLog() {
        TypicalDialog.dspdlg("Log", Util.logWriter.toString());
    }

    public synchronized void doAbout() {
        getAboutBox().show();
    }

    protected void load() {
        getDocumentManager().load();
    }

    protected void save() {
        getDocumentManager().save();
    }

    public Dimension getDefaultSize() {
        return new Dimension(800, 580);
    }

    public Properties getProperties() {
        return getDocumentManager().getProperties();
    }
}
